/*
 * Copyright 2011 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.gaellalire.jzip.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;

import fr.gaellalire.jzip.actions.FutureEntry;
import fr.gaellalire.jzip.model.utils.CreationListener;
import fr.gaellalire.jzip.model.utils.InZipFileUtils;
import fr.gaellalire.jzip.view.JZipActivitiesFrame;

/**
 *
 * @author Gael Lalire
 */
public class ZipTableModel extends AbstractTableModel implements CreationListener, Runnable {

    private static final long serialVersionUID = -9197235073659539350L;

    private Thread watcher = new Thread(this);

    private String[] columnNames = { "Nom", "Taille", "Compressé" };

    private File file;

    private ZipFile zipFile;

    private File tmpFile;

    private File contentFile;

    private InZipFile currentDirectory;

    private List<InZipFile> rootZipEntries = new ArrayList<InZipFile>();

    private List<InZipFile> zipEntries = new ArrayList<InZipFile>();

    private List<Object[]> rowData = new ArrayList<Object[]>();

    public ZipTableModel(final File file) throws IOException {
        this.file = file;
        this.zipFile = new ZipFile(file);
        tmpFile = File.createTempFile("jzip", ".tmp");
        tmpFile.delete();
        tmpFile.mkdir();
        tmpFile.deleteOnExit();
        contentFile = new File(tmpFile, "Content");
        contentFile.mkdir();
        contentFile.deleteOnExit();
        rootZipEntries = InZipFileUtils.create(zipFile);
        zipEntries = rootZipEntries;
        calculateRows();
        watcher.start();
    }

    public List<File> createFiles(final int[] selectedRows) throws IOException {
        File tmpDir = new File(contentFile, InZipFileUtils.uri(currentDirectory));
        tmpDir.mkdirs();
        List<File> files = new ArrayList<File>();
        for (int i : selectedRows) {
            files.add(InZipFileUtils.copyTo(zipFile, zipEntries.get(i), tmpDir, this));
        }
        return files;
    }

    @Override
    public Class<?> getColumnClass(final int columnIndex) {
        if (columnIndex == 0) {
            return JLabel.class;
        } else {
            return Long.class;
        }
    }

    @Override
    public String getColumnName(final int column) {
        return columnNames[column];
    }

    public int getRowCount() {
        return rowData.size();
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    public Object getValueAt(final int row, final int col) {
        return rowData.get(row)[col];
    }

    @Override
    public boolean isCellEditable(final int row, final int column) {
        return column == 0;
    }

    @Override
    public void setValueAt(final Object value, final int row, final int col) {
        rowData.get(row)[col] = value;
        fireTableCellUpdated(row, col);
    }

    private void calculateRows() {
        rowData.clear();
        for (InZipFile inZipFile : zipEntries) {
            if (inZipFile.isDirectory()) {
                rowData.add(new Object[] { new JLabel(inZipFile.getName(), UIManager.getIcon("FileView.directoryIcon"), JLabel.LEADING), null, null });
            } else {
                rowData.add(new Object[] { new JLabel(inZipFile.getName(), UIManager.getIcon("FileView.fileIcon"), JLabel.LEADING),
                        inZipFile.getZipEntry().getSize(), inZipFile.getZipEntry().getCompressedSize() });

            }
        }
        fireTableDataChanged();
    }

    public void select(final int row) throws IOException {
        InZipFile selectedInZipFile = zipEntries.get(row);
        if (selectedInZipFile.isDirectory()) {
            currentDirectory = selectedInZipFile;
            zipEntries = selectedInZipFile.getChildren();
            calculateRows();
        } else {
            String name = selectedInZipFile.getName();
            File tmpDir = new File(contentFile, InZipFileUtils.uri(currentDirectory));
            tmpDir.mkdirs();
            InZipFileUtils.copyTo(zipFile, selectedInZipFile, tmpDir, this);
            Process p = Runtime.getRuntime().exec(new String[] { "open", new File(tmpDir, name).getAbsolutePath() });
            BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            String s;
            while ((s = err.readLine()) != null) {
                System.err.println(s);
            }
        }
    }

    public void goParent() {
        if (currentDirectory == null) {
            return;
        }
        currentDirectory = currentDirectory.getParent();
        if (currentDirectory == null) {
            zipEntries = rootZipEntries;
        } else {
            zipEntries = currentDirectory.getChildren();
        }
        calculateRows();
    }

    public String getLocation() {
        return InZipFileUtils.uri(currentDirectory);
    }

    public void deleteAll(final File file) {
        File[] listFiles = file.listFiles();
        for (File subFile : listFiles) {
            if (subFile.isDirectory()) {
                deleteAll(subFile);
            } else {
                subFile.delete();
            }
        }
    }

    public void dispose() {
        watcher.interrupt();
        deleteAll(contentFile);
        try {
            zipFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @author Gael Lalire
     */
    private class Mapping {
        private File file;
        private long lastModified;
        private InZipFile inZipFile;

        public Mapping(final File file, final long lastModified, final InZipFile inZipFile) {
            this.file = file;
            this.lastModified = lastModified;
            this.inZipFile = inZipFile;
        }

        public boolean isModified() {
            return file.lastModified() != lastModified;
        }

    }

    private List<Mapping> mappings = new ArrayList<ZipTableModel.Mapping>();

    private Map<String, Mapping> modifiedMappings = new HashMap<String, ZipTableModel.Mapping>();

    @SuppressWarnings("unchecked")
    public void rebuild() throws IOException {
        final File rebuildFile = new File(tmpFile, "rebuild.zip");
        List<FutureEntry> futureEntries = new ArrayList<FutureEntry>();
        for (ZipArchiveEntry archiveEntry : Collections.list((Enumeration<ZipArchiveEntry>) zipFile.getEntries())) {
            String name = archiveEntry.getName();
            Mapping mapping = modifiedMappings.get(name);
            if (mapping != null) {
                futureEntries.add(new FutureEntry(name, mapping.file.length(), new FileInputStream(mapping.file)));
            } else {
                futureEntries.add(new FutureEntry(name, archiveEntry.getSize(), zipFile.getInputStream(archiveEntry)));
            }
        }
        for (BuildStateListener buildStateListener : buildStateListeners) {
            buildStateListener.buildStateChange(BuildState.IN_CREATION);
        }
        JZipActivitiesFrame.compressTo(futureEntries, new FileOutputStream(rebuildFile), new Runnable() {

            @Override
            public void run() {
                Set<Entry<String, Mapping>> entrySet = modifiedMappings.entrySet();
                for (Entry<String, Mapping> entry : entrySet) {
                    mappings.add(new Mapping(entry.getValue().file, entry.getValue().file.lastModified(), entry.getValue().inZipFile));
                }
                modifiedMappings.clear();
                for (BuildStateListener buildStateListener : buildStateListeners) {
                    buildStateListener.buildStateChange(BuildState.NOT_MODIFIED);
                }
                try {
                    zipFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (file.delete()) {
                    rebuildFile.renameTo(file);
                    try {
                        zipFile = new ZipFile(file);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    public void run() {
        while (true) {
            boolean rebuildActivated = true;
            if (modifiedMappings.size() == 0) {
                rebuildActivated = false;
            }
            Iterator<Mapping> iterator = mappings.iterator();
            while (iterator.hasNext()) {
                Mapping mapping = iterator.next();
                if (mapping.isModified()) {
                    modifiedMappings.put(mapping.inZipFile.getZipEntry().getName(), mapping);
                    iterator.remove();
                }
            }
            if (!rebuildActivated && modifiedMappings.size() != 0) {
                for (BuildStateListener buildStateListener : buildStateListeners) {
                    buildStateListener.buildStateChange(BuildState.MODIFIED);
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    private List<BuildStateListener> buildStateListeners = new ArrayList<BuildStateListener>();

    public void addBuildStateListener(final BuildStateListener buildStateListener) {
        buildStateListeners.add(buildStateListener);
    }

    @Override
    public void create(final File file, final InZipFile inZipFile) {
        long lastModified = file.lastModified();
        mappings.add(new Mapping(file, lastModified, inZipFile));
    }
}
